<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8">
  <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
  <meta name="baidu-site-verification" content="code-UjJKh3jtOo">
  <link rel="stylesheet" href="//unpkg.com/@highlightjs/cdn-assets@11.5.0/styles/atom-one-dark.min.css">
  <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
  <title>Tit1e</title>
  <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-2767111597631391" crossorigin="anonymous"></script>
  
<link rel="stylesheet" href="/css/style.css">

<meta name="generator" content="Hexo 6.3.0"><link rel="alternate" href="/atom.xml" title="Tit1e" type="application/atom+xml">
</head>

<body class="dark">
    <div class="container">
    <header class="header">
  <div class="title">
      <a href="/" class="logo">Tit1e</a>
      <div class="btn-dark"></div>
      <script>
        let bodyClx = document.body.classList;
        let btnDark = document.querySelector('.btn-dark');
        let sysDark = window.matchMedia('(prefers-color-scheme: dark)');
        let darkVal = localStorage.getItem('dark');

        let setDark = (isDark) => {
        bodyClx[isDark ? 'add' : 'remove']('dark');
        localStorage.setItem('dark', isDark ? 'yes' : 'no');
        };

        setDark(darkVal ? darkVal === 'yes' : sysDark.matches);
        requestAnimationFrame(() => bodyClx.remove('not-ready'));

        btnDark.addEventListener('click', () => setDark(!bodyClx.contains('dark')));
        sysDark.addEventListener('change', (event) => setDark(event.matches));
      </script>
  </div>
    <ul class="menu">
        
        
        
        
        <li class="menu-item ">
            <a href="/" class="menu-item-link">Home</a>
        </li>
        
        
        <li class="menu-item ">
            <a href="/archives/" class="menu-item-link">Archives</a>
        </li>
        
        
        <li class="menu-item ">
            <a href="/atom.xml" class="menu-item-link">RSS</a>
        </li>
        
        
        <li class="menu-item ">
            <a target="_blank" rel="noopener" href="https://pixeltimer.art" class="menu-item-link">Pixel Timer</a>
        </li>
        
    </ul>
    <div class="search-box">
        <input type="search" id="search" placeholder="搜索">
    </div>
</header>
    <article class="post">
  <div class="post-meta">
    <span class="post-time">3 / 26, 2019</span>
  </div>
  <div class="post-title">
    <h1 class="title">使用 Vue CLI3 编写组件库并发布至 npm</h1>
  </div>
  
  <ul class="post-tags">
    
    <li class="tag-item">
      #学习
    </li>
    
    <li class="tag-item">
      #vue
    </li>
    
  </ul>
  
  <div class="post-content">
    <p>前几天研发部老大来跟我说，由于现在公司业务中台化，基本上公司所有的系统都要进行迁移，并将原来使用的jQuery + Bootstrap 技术栈更新为 Vue + Element。既然统一了技术栈，那么在开发过程中肯定会有很多组件是可以共用的，甚至登录页。因此要求我开发一个公共组件库，以避免开发过程中写重复代码，提高开发效率。我也正好趁此机会了解一下相关的知识。</p>
<p>先放上组件地址：<a target="_blank" rel="noopener" href="https://www.npmjs.com/package/hzsj-components">hzsj-components</a>。因为不涉及公司业务，所以就把它发布到了 npm 上。</p>
<p>这个组件库是在  <a target="_blank" rel="noopener" href="http://element-cn.eleme.io/#/zh-CN">Element</a> 的基础上进行开发的，而且是第一次开发，不了解如何合理配置目录，因此我的组件库目录部分参考了 <a target="_blank" rel="noopener" href="https://github.com/ElemeFE/element">element-ui</a> 的结构。</p>
<span id="more"></span>

<p>以  <code>HzCopy</code> 组件为例：</p>
<p>所有组件都放置在 <code>/packages</code> 目录下，<code>/packages/index.js</code> 用于整合所有组件，导出整个组件库。而 <code>/packages/copy/index.js</code> 用于导出单个组件，比如需要按需引入组件时。</p>
<pre><code class="javascript">// /packages/index.js
import Copy from &#39;./copy&#39;

// 存储组件列表
const components = [
  Copy
]

// 定义 install 方法，接收 Vue 作为参数。如果使用 use 注册插件，则所有的组件都将被注册
const install = function (Vue) &#123;
  // 判断是否安装
  if (install.installed) return
  // 遍历注册全局组件
  components.map(component =&gt; Vue.component(component.name, component))
&#125;

// 判断是否是直接引入文件
if (typeof window !== &#39;undefined&#39; &amp;&amp; window.Vue) &#123;
  install(window.Vue)
&#125;

export default &#123;
  install,
  Copy
&#125;
</code></pre>
<pre><code class="javascript">// /packages/copy/index.js
// 导入组件，组件必须声明 name
import HzCopy from &#39;./src&#39;

// 为组件提供 install 安装方法，供按需引入
HzCopy.install = function(Vue) &#123;
  Vue.component(HzCopy.name, HzCopy)
&#125;

// 默认导出组件
export default HzCopy
</code></pre>
<p>这样就编写完了一个组件，但要发布还得做一些其他工作。</p>
<p>完善 <code>package.json</code>：</p>
<ul>
<li><p>name：包名，就是  <code>npm install</code>  的时候输入的包名</p>
</li>
<li><p>version：版本</p>
</li>
<li><p>description：对包的描述，有利于用户搜索</p>
</li>
<li><p>keywords：关键字，以空格分离，有利于搜索</p>
</li>
<li><p>private：是否私有，必须修改为 false</p>
</li>
<li><p>author：作者</p>
</li>
<li><p>license：开源协议</p>
</li>
<li><p>repository：在 npm 介绍页面显示项目入口，参考格式</p>
<pre><code class="json">&quot;repository&quot;: &#123;
  &quot;type&quot;: &quot;git&quot;,
  &quot;url&quot;: &quot;https://github.com/xxxx/xxxx.git&quot;
&#125;
</code></pre>
</li>
<li><p>scripts：新增一条打包库的命令</p>
<ul>
<li><code>--target</code>： 构建目标，默认为应用模式。这里修改为 <code>lib</code> 启用库模式。</li>
<li><code>--dest</code>：输出目录，默认 <code>dist</code>。这里我们改成 <code>lib</code>。</li>
<li><code>[entry]</code>： 最后一个参数为入口文件，默认为  <code>src/App.vue</code>。这里我们指定编译  <code>packages/</code>  组件库目录。</li>
</ul>
<pre><code class="json">&quot;scripts&quot;: &#123;
  &quot;lib&quot;: &quot;vue-cli-service build --target lib --name hzsj-components --dest lib packages/index.js&quot;
&#125;
</code></pre>
</li>
<li><p>main：定义包的入口文件</p>
<pre><code class="json">&quot;main&quot;: &quot;lib/hzsj-components.umd.min.js&quot;
</code></pre>
</li>
<li><p>engines：告诉用户运行你的包对 NodeJs 版本的要求</p>
<pre><code class="json">&quot;engines&quot;: &#123;
  &quot;node&quot;: &quot;v11.8.0&quot;
&#125;
</code></pre>
</li>
</ul>
<p>修改好  <code>package.json</code> 后就可以准备发布包了。</p>
<p>首先如果使用了淘宝或其他镜像需先设置回 npm 镜像</p>
<pre><code class="bash">npm config set registry http://registry.npmjs.org 
</code></pre>
<p>或者如果使用 <a target="_blank" rel="noopener" href="https://www.npmjs.com/package/nrm">nrm</a> 可使用：</p>
<pre><code class="bash">nrm use npm
</code></pre>
<p>然后去 <a target="_blank" rel="noopener" href="https://www.npmjs.com/">npm</a> 注册一个账号，注册完了在终端登录</p>
<pre><code class="bash">npm login
</code></pre>
<p>发布</p>
<pre><code class="bash">npm publish
</code></pre>
<p>如果你的包名以 @ 开头，用上面命令可能会发布失败，需要用以下命令发布</p>
<pre><code class="bash">npm publish --access public
</code></pre>
<p><a target="_blank" rel="noopener" href="https://juejin.im/post/5bbab9de5188255c8c0cb0e3">参考文章</a></p>
<p><a target="_blank" rel="noopener" href="https://juejin.im/post/5b231f6ff265da595f0d2540">参考文章2</a></p>
<p><a target="_blank" rel="noopener" href="https://blog.csdn.net/u013727805/article/details/80849329">参考文章3</a></p>

  </div>
  <div class="post-near">
    
    <a
      class="post-near-prev"
      href="/p/2a9d6337.html"
      title="网页打印指定区域内容"
    >
      <span class="arrow">←</span>
      <span class="post-near-title">
        网页打印指定区域内容
      </span>
    </a>
    
    
    <a
      class="post-near-next"
      href="/p/9660caa6.html"
      title="在 Vue CLI 3 中配置 svg-sprite-loader"
    >
      <span class="post-near-title">
        在 Vue CLI 3 中配置 svg-sprite-loader
      </span>
      <span class="arrow">→</span>
    </a>
    
  </div>
</article>

    <footer class="footer">
  <p>本博客采用 <a class="license-link" rel="license noopener" target="_blank" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh">署名-非商业性使用-相同方式共享 4.0 国际</a>协议进行许可</p>
  <p>
    <span>Thanks For <a target="_blank" rel="noopener" href="https://github.com/nanxiaobei/hugo-paper" taget="_blank">hugo-paper</a></span>
    <span style="margin-left: 10px;">&copy; 2016-2025 by Tit1e</span>
  </p>
</footer>
    </div>
    <script src="//unpkg.com/@highlightjs/cdn-assets@11.5.0/highlight.min.js"></script>
<script>
  hljs.initHighlightingOnLoad();
  var _hmt = _hmt || [];
    (function() {
    var hm = document.createElement("script");
    hm.src = "https://hm.baidu.com/hm.js?6349d3bc54baea0b04a65145c5e3b799";
    var s = document.getElementsByTagName("script")[0];
    s.parentNode.insertBefore(hm, s);
    })();
    const block = document.querySelectorAll('.post-content')[0]
    block.addEventListener('click', e => {
        const {nodeName, src} = e.target
        if(nodeName !== 'IMG') return
        document.body.classList.add('overflow-hidden')
        const div = document.createElement('div')
        div.id = 'global-cover'
        div.title = '点击关闭'
        const a = document.createElement('a')
        a.href = src
        a.target="_blank"
        a.title = '点击打开原图'
        const img = document.createElement('img')
        img.className = 'show-pic'
        img.src = src
        a.appendChild(img)
        div.appendChild(a)
        document.body.appendChild(div)
    })
    document.body.addEventListener('click', e => {
        if(e.target.id === 'global-cover'){
            document.body.classList.remove('overflow-hidden')
            document.body.removeChild(e.target)
        }
    })
</script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-7M04VN3JZ4"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', 'G-7M04VN3JZ4');
</script>

<script src="/js/search.js"></script>


</body>
</html>